home *** CD-ROM | disk | FTP | other *** search
Text File | 1994-02-10 | 9.7 KB | 301 lines | [TEXT/KAHL] |
- /* See the file Distribution for distribution terms.
- (c) Copyright 1994 Ari Halberstadt */
-
- /* Implements exception handling using ANSI C. The macro names were inspired
- by the exception handling mechanism in THINK C, but the code is original.
- Any number of nested exception handlers may be used within any function.
- The basic syntax for exception handling is:
-
- TRY {
- ... main body of routine ...
- } CLEANUP {
- ... code to execute on success and on failure ...
- } CATCH {
- ... code to execute only on failure ...
- } ENDTRY;
-
- Exceptions are raised using the RAISE macro, though you usually will not
- use the RAISE macro directly. Several functions are provided for raising
- exceptions, such as FailOSErr which raises an exception when an operating
- system error occurs, and FailNIL which raises an exception if a pointer
- is NULL. You will usually rely on these FailXxxx functions to raise
- exceptions.
-
- The CLEANUP handler is optional, while the TRY, CATCH, and ENDTRY
- portions are required. If a CLEANUP handler is provided, it must
- follow the TRY handler and precede the CATCH handler.
-
- When an exception is raised, execution jumps to the CLEANUP or CATCH
- handler corresponding to the most recently executed TRY statement.
- The code of the CLEANUP handler, if any, is executed first. Execution
- then proceeds to the code of the CATCH handler. When the ENDTRY statement
- is executed, another exception is raised, causing execution to proceed
- with the next enclosing CLEANUP or CATCH handler. This process repeats
- until a RETRY or NOPROPAGATE statement is executed. The application must
- provide a RETRY or NOPROPAGATE statement in some top-level handler
- (which generally will exit the application), or else the program will
- crash when the stack of handlers has been emptied. If an exception is
- raised while executing a CLEANUP or CATCH handler, then execution
- proceeds with the next enclosing CLEANUP or CATCH handler.
-
- According to the documentation provided with THINK C, any local variable
- whose value may have changed between a call to setjmp and the call to
- longjmp must be declared volatile to ensure that it contains the correct
- value. Since the TRY macro calls setjmp, and the RAISE macro calls longjmp,
- any variables used within the CATCH or CLEANUP handlers, and which may
- have changed value during execution of the function, must be either
- of static or global scope, or they must be declared to be volatile.
-
- The TRY part of the exception handler can be retried using the RETRY
- macro. For instance,
-
- long SomeFunction(...)
- {
- volatile Boolean failed = false;
- long result;
-
- TRY {
- result = GetDefaultValue();
- if (! failed) {
- ... try algorithm ...
- }
- } CATCH {
- if (! failed) {
- failed = true;
- RETRY;
- }
- } ENDTRY;
- return(result);
- }
-
- There are several points worth noting about this example. First, we use
- a variable 'failed' (declared volatile) to indicate that the initial
- attempt failed and that we should therefore use a default value. Second,
- the flag serves to prevent a possible infinite loop. For instance, if
- the function GetDefaultValue() could itself fail, and if we didn't check
- that we had already failed in the CATCH handler,
-
- ...
- } CATCH {
- RETRY;
- } ENDTRY;
- ...
-
- then we would enter an infinite loop, since we would retry the operation,
- which would call GetDefaultValue, which would fail, etc. Third, since
- the 'result' variable is a local variable, we have to be careful
- to reinitialize it inside the TRY handler.
-
- It is good programming practice to write functions so that they either
- succeed or fail. It is much harder to validate software when functions
- have the third alternative of neither succeeding nor failing, but of
- just doing some undefined action. By definition, a function succeeds
- when it fulfills its postcondition, and fails when it is doesn't
- fulfill its postcondition (whether by the violation of an assertion,
- the failure of some computer resource, invalid data, or due to any other
- reason). A failed function results in a raised exception, which is
- handled by the next CATCH handler. Following a failure, most functions
- will do some minimal housekeeping, such as closing files they had opened,
- and then simply allow the failure to propagate to the next CATCH handler.
- Some functions may attempt to execute with a different alogrithm, or may
- return some default value, or may take any other action necessary to fulfill
- their postcondition; these actions are best executed by setting a flag
- to indicate failure and then using the RETRY macro.
-
- In some situations it may be necessary to use the NOPROPAGATE macro.
- For instance, an Apple event handler function must return an error code
- to the Apple Event Manager. This can be accomplished as follows:
-
- pascal OSErr HandleAEOpenApplication(AppleEvent *event,
- AppleEvent *reply, long refcon)
- {
- OSErr err = noErr;
-
- TRY {
- AEGotRequiredParameters(event);
- // handle the event as appropriate for the application
- } CATCH {
- err = FailReason();
- NOPROPAGATE;
- } ENDTRY;
- return(err);
- }
-
- Since this function returns an error code, it still meets the criteria
- of either succeeding or failing. We're just using the FailReason
- function and the NOPROPAGATE macro to translate between an error handling
- method based on exceptions an error handling method based on function
- return values.
-
- It is helpful to users to provide additional information about the
- operation being attempted when a failure occurred. This information
- can then be displayed in an alert. Usually the top-level failure handler
- in the application will do some house cleaning and then call FailDisplay.
- FailDisplay uses the information set by the application to format a
- string containing as much information as possible about the failure
- and then displays it in an alert. After calling FailDisplay you should call
- FailClear to reset the failure information so that the failure isn't
- reported to the user more than once.
-
- The function FailInfoSet can be used by the application to describe
- the operation that was attempted. For instance, when opening a file
- you might use
-
- void OpenFile(FileType *fp)
- {
- FailInfoSet(RLS_ERR_READ, FileName(fp), NULL);
- ... open file ...
- FailInfoClear(); // clear info so next error doesn't display old info
- }
-
- The parameters to FailInfoSet, along with the error code (if any) that
- caused the failure, are passed to the function ErrorDisplay. See
- ErrorDisplay (in ErrorLib.c) for a description of how error messages
- are formatted and displayed. */
-
- /*
- 94/02/10 aih - added ExceptionCopy to support thread context switches
- 94/02/08 aih - RAISE macro checks for null jmpenv to keep from crashing
- if there's no top-level NOPROPAGATE statement
- 94/01/28 aih - added big usage comment
- 94/01/19 aih - defined ExceptionTryType in ExceptionLib.h
- - added some comments to ExceptionLib.h
- 94/01/12 aih - added a few functions for setting information describing
- a failure
- 94/01/10 aih - made exception data a global variable
- 93/11/16 aih - fixed an error in the setup code which didn't properly
- reset gExecetion.jmpenv after a failure
- - added a comment describing a _try structure
- 93/10/26 aih - simplified macros a bit, fixed bug with prior bug fix...
- 93/10/23 aih - fixed bug with recursive failures/retries
- 93/10/19 aih - improved error reporting
- 93/03/?? aih - created */
-
- #if WINTER_SHELL
- #include "ErrorLib.h"
- #else /* WINTER_SHELL */
- typedef char CStr255[sizeof(Str255)];
- static void ErrorDisplay(OSErr err, short action, const CStr255 object,
- const CStr255 explanation)
- {
- /* display an error message */
- }
- #endif /* WINTER_SHELL */
-
- #include <string.h>
- #include <PrintTraps.h>
- #include "ExceptionLib.h"
-
- ExceptionType gException; /* information about current exception */
-
- /* Copy the exception data from one structure to another. Since most of
- the structure is just a a couple of big empty strings, this is more
- efficient than doing a structure-to-structure assignment. This function
- MUST NOT MOVE MEMORY. This function is primarily for use during
- context switches by the thread library, you should never have to call
- this function. */
- void ExceptionCopy(const ExceptionType *src, ExceptionType *dst)
- {
- register const char *s;
- register char *t;
-
- dst->jmpenv = src->jmpenv;
- dst->err = src->err;
- dst->action = src->action;
- s = src->object;
- t = dst->object;
- while (*t++ = *s++)
- ;
- s = src->explanation;
- t = dst->explanation;
- while (*t++ = *s++)
- ;
- }
-
- void FailActionSet(short action)
- {
- gException.action = action;
- }
-
- void FailObjectSet(const CStr255 object)
- {
- *gException.object = 0;
- if (object)
- strcpy((char *) gException.object, object);
-
- }
-
- void FailExplanationSet(const CStr255 explanation)
- {
- *gException.explanation = 0;
- if (explanation)
- strcpy(gException.explanation, explanation);
- }
-
- void FailInfoSet(short action, const CStr255 object, const CStr255 explanation)
- {
- FailActionSet(action);
- FailObjectSet(object);
- FailExplanationSet(explanation);
- }
-
- void FailInfoClear(void)
- {
- FailInfoSet(0, NULL, NULL);
- }
-
- void FailClear(void)
- {
- gException.err = noErr;
- FailInfoClear();
- }
-
- void FailDisplay(void)
- {
- if (gException.err != userCanceledErr)
- ErrorDisplay(gException.err, gException.action,
- gException.object, gException.explanation);
- }
-
- OSErr FailReason(void)
- {
- return(gException.err);
- }
-
- void FailOSErr(OSErr err)
- {
- if (err) {
- if (err == iPrAbort)
- err = userCanceledErr;
- if (! gException.err)
- gException.err = err;
- RAISE;
- }
- }
-
- void FailMemError(void)
- {
- FailOSErr(MemError());
- }
-
- void FailResError(void)
- {
- FailOSErr(ResError());
- }
-
- void FailPrError(void)
- {
- FailOSErr(PrError());
- }
-
- void FailNIL(void *p)
- {
- if (! p) FailOSErr(MemError() ? MemError() : memFullErr);
- }
-
- void FailNILRes(void *p)
- {
- if (! p) FailOSErr(ResError() ? ResError() : resNotFound);
- }
-
-